
# TODO - add counters per ISBN so missing markup doesn't get stuck in a loop
# TODO - fetch publisher (and rating?)

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import time
import thread_function


from BeautifulSoup import BeautifulSoup, SoupStrainer
import re
import urllib
import os
from os.path import exists, isfile
import sys
import getopt
import config

class MissingMarkupWarning(Exception):
    def __init__(self, text=None):
        self.text = text


def print_timing(func):
    def wrapper(*arg):
        t1 = time.time()
        res = func(*arg)
        t2 = time.time()
        print '%s took %0.3f ms' % (func.func_name, (t2-t1)*1000.0)
        return res
    return wrapper


#@print_timing
def get_url_soup(url):
    markup = get_markup_from_url(url)
    soup = BeautifulSoup(markup)
    return soup

def get_markup_from_url(url):
    u = urllib.urlopen(url)
    markup = u.read()
    u.close()
    if len(markup) < 10:
        raise MissingMarkupWarning()
    return markup

def get_title_from_soup(soup):
    item = soup.findAll('span', id='btAsinTitle')
    if not item:
        print 'Error - Missing tag span for title'
        return 'Unknown Title'
    title = item[0].string
    title = title.strip()
    return title
    
def get_author_from_soup(soup):
    item = soup.findAll('a', href=re.compile('field-author'))
    if not item:
        print 'Missing tag a for author'
        return 'Unknown Author'
    author = item[0].string
    author = author.strip()
    return author
    
def get_img_src_from_soup(soup):
    """
    Return the url for the image data
    """
    item = soup.findAll('img', id='prodImage')
    if not item:
        print "Error - Tag missing"
        return None
    img_src = re.compile('(http.*.jpg)').findall(str(item))
    if not img_src:
        print "Error - RegExp did not find JPG"
        return None
    img = re.compile('(http.*?_.*?_)').findall(img_src[0])
    if not img:
        print "Error - RegExp found JPG but did not recognise URL format"
        return None
    img_url = img[0] + '.jpg'
    return img_url

def get_data(isbn):
    """
    retrieve data from amazon for this ISBN
    @rtype: [title, author, img_src]
    """
    print "Fetching data for ISBN: ",  isbn
    search_url = 'http://www.amazon.co.uk/gp/search?keywords='+isbn+'&index=books'
    soup = get_url_soup(search_url)
    img_src = get_img_src_from_soup(soup)
    title = get_title_from_soup(soup)
    author = get_author_from_soup(soup)

    if img_src:
        # go get image
        filename = config.img_dir + '/' + isbn + '.jpg'
        if not exists(config.img_dir):
            os.mkdir(config.img_dir)
        fetch_image_to_file(img_src, filename)

    return [title, author]


def fetch_image_to_file(img_url, local_file):
    try:
        if exists(local_file):
            if isfile(local_file):
                os.remove(local_file)
        urllib.urlretrieve(img_url, local_file)
        return True
    except Exception, e:
        print "Problem retrieving file: ", img_url
        print e
        return False



def fetch_data(isbn):
    """
    Fetch the data.
    If problems occur due that could be solved by retrying - indicate this 
    """
    res = None
    try:
        res = get_data(isbn)
    except MissingMarkupWarning, e:
        res = 'MissingMarkup'
    return res





class ISBNItem:
    def __init__(self, isbn):
        self.isbn = isbn
        self._status = 'Pending'

    def isPending(self):
        if self._status == 'Pending':
            return True
        return False

    def setWorking(self):
        self._status = 'Working'
    
    def status(self):
        return self._status

#def fetch_data(isbn):
#    time.sleep(.2)
#    return [isbn, 'title']

class ISBNDispatcher(QThread):
    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.pending_mutex = QMutex()
        self.result_mutex = QMutex()
        self.isbn_pending_queue = []
        self.isbn_pending_dict = {}
        self.amazon_result_queue = []

    def next_pending_isbn(self):
        locker = QMutexLocker(self.pending_mutex)
#        print 'Checking for pending isbn'
        for item in self.isbn_pending_queue:
            if item.isPending():
#                print 'set working'
                item.setWorking()
                return item.isbn
        return None

    def isbn_submit(self, isbn):
        locker = QMutexLocker(self.pending_mutex)
#        print 'submitting isbn', isbn
        if not isbn in self.isbn_pending_dict:
            self.isbn_pending_queue.append(ISBNItem(isbn))
            self.isbn_pending_dict[isbn]=True

    def isbn_completed(self, isbn):
        locker = QMutexLocker(self.pending_mutex)
        for i, item in enumerate(self.isbn_pending_queue):
                if item.isbn == isbn:
                    del self.isbn_pending_queue[i]
                    del self.isbn_pending_dict[isbn]
                    break
          
#    @print_timing
    def service_pending_queue(self):
#        print 'Servicing pending queue'
        next_isbn = self.next_pending_isbn()
        if next_isbn:
            QMutexLocker(self.result_mutex)
            self.amazon_result_queue.append([next_isbn, thread_function.Future(fetch_data, next_isbn)])
            
#    @print_timing
    def service_result_queue(self):
        """
        Process all finished results from the result queue
        """
#        print 'Servicing result queue'
        locker = QMutexLocker(self.result_mutex)
        completed_isbn = []
        for i in range(len(self.amazon_result_queue)):
            isbn, thread_result = self.amazon_result_queue.pop(0)
            if thread_result.isDone():
                res = thread_result()

                if res == None:
                    print isbn, ': No result'
                    completed_isbn.append(isbn)
                t = type(res)
                if t == list:
                    data = [isbn]
                    data.extend(res)
                    self.emit(SIGNAL('isbn_data'), data)
                    completed_isbn.append(isbn)
                elif t == str:
                    if res == 'MissingMarkup':
                        print isbn, ': Markup was missing - retrying'
                        self.amazon_result_queue.append([isbn, thread_function.Future(fetch_data, isbn)])
            else:
                self.amazon_result_queue.append([isbn, thread_result])
        del locker
        for isbn in completed_isbn:
            self.isbn_completed(isbn)
               
            
    def run(self):
        while True:
#            print "Start iteration"
            self.service_pending_queue()
            time.sleep(0.5)
            self.service_result_queue()
#            print "End iteration"
                
class MyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.textedit = QTextEdit()
        self.setCentralWidget(self.textedit)

    def add_data(self, data):
        self.textedit.insertPlainText(QString(str(data)) + QString('\n'))
        
#@print_timing
def dispatch_from_file(filename, dispatcher):
    file = open(filename)
    for isbn in file:
        isbn = isbn.strip('\n')
        dispatcher.isbn_submit(isbn)
#        res = a()
#        print count, ": ", isbn, ": ", res
#        count += 1
#        if res == None:
#            print isbn, 'No result'
#        t = type(res)
#        if t == list:
#            print isbn, res
#        elif t == str:
#            if res == 'MissingMarkup':
#                print isbn, ': Markup was missing - retrying'
#                futures.append([isbn, future.Future(fetch_data, isbn)])
 
                
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    dispatcher = ISBNDispatcher(app)
    app.connect(dispatcher,SIGNAL('isbn_data'), window.add_data)
    dispatcher.start()
    dispatch_from_file('isbn3.txt', dispatcher)
    sys.exit(app.exec_())

                
            
